
#include "StreamConnection.h"

//#define STREAM_TO_FILE "D:\\mount.ogg"

static char base64table[65] = {
	'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
	'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
	'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
	'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/',
};

/* This isn't efficient, but it doesn't need to be */
char *base64_encode(char *data)
{
	int len = strlen(data);
	char *out = new char[(len*4/3 + 4)];
	char *result = out;
	int chunk;

	while(len > 0) {
		chunk = (len >3)?3:len;
		*out++ = base64table[(*data & 0xFC)>>2];
		*out++ = base64table[((*data & 0x03)<<4) | ((*(data+1) & 0xF0) >> 4)];

		switch(chunk) {
		case 3:
			*out++ = base64table[((*(data+1) & 0x0F)<<2) | ((*(data+2) & 0xC0)>>6)];
			*out++ = base64table[(*(data+2)) & 0x3F];
			break;
		case 2:
			*out++ = base64table[((*(data+1) & 0x0F)<<2)];
			*out++ = '=';
			break;
		case 1:
			*out++ = '=';
			*out++ = '=';
			break;
		}
		data += chunk;
		len -= chunk;
	}
	*out = 0;

	return result;
}

StreamConnection::StreamConnection() :
	m_strIP(0), m_strUser(0), m_strPass(0), m_strMountPoint(0), m_nPort(0), m_strName(0),
	m_strAgent(0), m_strDesc(0), m_strGenre(0), m_strURL(0), m_nFormat(0), m_nPublic(0)
	, shout(0), m_bExit(1)
{
}

int StreamConnection::setHost(const char *host)
{
	STR_CPY(m_strIP, host);
	return 0;
}

int StreamConnection::setPort(const int port)
{
	m_nPort = port;
	return 0;
}

int StreamConnection::setUser(const char *user)
{
	STR_CPY(m_strUser, user);
	return 0;
}

int StreamConnection::setPass(const char *pass)
{
	STR_CPY(m_strPass, pass);
	return 0;
}

int StreamConnection::setMountPoint(const char *mount)
{
	STR_CPY(m_strMountPoint, mount);
	return 0;
}

int StreamConnection::setName(const char *name)
{
	STR_CPY(m_strName, name);
	return 0;
}

int StreamConnection::setAgent(const char *agent)
{
	STR_CPY(m_strAgent, agent);
	return 0;
}

int StreamConnection::setDescription(const char *desc)
{
	STR_CPY(m_strDesc, desc);
	return 0;
}

int StreamConnection::setGenre(const char *genre)
{
	STR_CPY(m_strGenre, genre);
	return 0;
}

int StreamConnection::setURL(const char *url)
{
	STR_CPY(m_strURL, url);
	return 0;
}

int StreamConnection::setFormat(const int format)
{
	m_nFormat = format;
	return 0;
}

int StreamConnection::setPublic(const int pub)
{
	m_nPublic = pub;
	return 0;
}

StreamConnection::~StreamConnection(void)
{
	// close current connection
	close();

	// exit thread
	if (!m_bExit)
	{
		m_bExit = 1;
		while (!isFinished())
			QThread::msleep(100);
	}

	// delete data
	while (m_queueData.size() > 0)
	{
		SendData *tmp = m_queueData.dequeue();
		delete [] tmp->data;
		delete tmp;
	}
}

int StreamConnection::connectShout()
{
	// close current connection
	close();

	// initialize connection
	return initializeShout();
}

int StreamConnection::close()
{
	if (shout)
	{
		shout_close(shout);
		shout_free(shout);
		shout = 0;
	}

	return 0;
}

int StreamConnection::connect()
{
	if (!m_strIP || !m_nPort || !m_strUser || !m_strPass)
		return 2;

	#define LARGE_BUF_SIZE 2048
	#define SMALL_BUF_SIZE 512
	char szBuffer[LARGE_BUF_SIZE], szChunkBuf[SMALL_BUF_SIZE];
	int size = 0;

	// header
	sprintf_s(szBuffer, LARGE_BUF_SIZE, "SOURCE %s HTTP/1.0\r\n", m_strMountPoint);

	// authenticate
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "%s:%s", m_strUser, m_strPass);
	char *szAuthBuf64 = base64_encode(szChunkBuf);
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "Authorization: Basic %s\r\n", szAuthBuf64);
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);
	delete [] szAuthBuf64;

	// user agent
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "User-Agent: %s\r\n", m_strAgent ? m_strAgent : "IceCast Streamer");
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// content type
	strcat_s(szBuffer, LARGE_BUF_SIZE, "Content-Type: application/ogg\r\n");
	strcat_s(szBuffer, LARGE_BUF_SIZE, "ice-subtype: Vorbis/Theora\r\n");

	// stream name
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "ice-name: %s\r\n", m_strName ? m_strName : "Noname");
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// cast URL
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, m_strURL ? "ice-url: %s\r\n" : "ice-url: http://%s:%d/%s\r\n", m_strURL ? m_strURL : m_strIP, m_nPort, m_strMountPoint);
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// genre
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "ice-genre: %s\r\n", m_strGenre ? m_strGenre : "General");
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// public mode
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "ice-public: %d\r\n", m_nPublic);
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// description
	sprintf_s(szChunkBuf, SMALL_BUF_SIZE, "ice-description: %s\r\n", m_strDesc ? m_strDesc : "No desc");
	strcat_s(szBuffer, LARGE_BUF_SIZE, szChunkBuf);

	// done
	strcat_s(szBuffer, LARGE_BUF_SIZE, "\r\n");

	// send to server
	if ((size = sendShout((const unsigned char*)szBuffer, strlen(szBuffer) + 1)) < 0)
		return 4;

	// get response
	memset(szBuffer, 0, LARGE_BUF_SIZE);

	return 0;
}

int StreamConnection::initializeShout()
{
	shout_init();

	char szErrBuf[1024];
	if (!(shout = shout_new()))
		return 1;

	if (shout_set_host(shout, m_strIP) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting host name: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 2;
	}

	if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting protocol: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 3;
	}

	if (shout_set_port(shout, m_nPort) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting port: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 4;
	}

	if (shout_set_password(shout, m_strPass) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting password: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 5;
	}

	if (shout_set_mount(shout, m_strMountPoint) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting mount: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 6;
	}

	if (shout_set_user(shout, m_strUser) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting user: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 7;
	}

	if (shout_set_format(shout, SHOUT_FORMAT_OGG) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting format: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 8;
	}

	if (shout_set_agent(shout, TITLE_FORM_MAIN) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting agent: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 9;
	}

	if (shout_set_description(shout, "Video streamed by " TITLE_FORM_MAIN) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting description: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 10;
	}

	if (shout_set_public(shout, 1) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting public: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 11;
	}

	if (shout_set_genre(shout, "General") != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting genre: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 12;
	}

	char szURL[1024];
	sprintf(szURL, "http://%s:%d/%s", m_strIP, m_nPort, m_strMountPoint);
	if (shout_set_url(shout, szURL) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error setting url: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 13;
	}

#ifdef STREAM_TO_FILE
	if (shout_set_dumpfile(shout, STREAM_TO_FILE) != SHOUTERR_SUCCESS)
	{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Cannot set dump file: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		return 14;
	}
#endif

	if (shout_open(shout) != SHOUTERR_SUCCESS) {
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		sprintf(szErrBuf, "Error connecting: %s\n", shout_get_error(shout));
		emit logged(szErrBuf);
#endif
		close();
		return 15;
	}

	return 0;
}

int StreamConnection::sendShout(const unsigned char* header, const int header_len,const unsigned char *data, const int size)
{
	if (!shout)
	{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		char szErrBuf[MAX_LEN];
		sprintf_s(szErrBuf, MAX_LEN, "Invalid shout object");
		emit logged(szErrBuf);
#endif
		return 1;
	}

	if (!data || size <= 0)
	{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		char szErrBuf[MAX_LEN];
		sprintf_s(szErrBuf, MAX_LEN, "Invalid data: pointer=0x%.8x, size=%d", data, size);
		emit logged(szErrBuf);
#endif
		return 2;
	}

	if (shout_send(shout, header, header_len) != SHOUTERR_SUCCESS) {
		return -1;
	}

	if (shout_send(shout, data,size) != SHOUTERR_SUCCESS) {
		return -1;
	}

	if(shout_queuelen(shout) > 0) 
	{
		//fprintf(stderr, MNL "OGGFWD - queue length: %lu\n", (unsigned long) shout_queuelen(myShout));
	}
	shout_sync(shout);

	return 0;
}

int StreamConnection::sendShout(const unsigned char *data, const int size)
{
	if (!shout)
	{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		char szErrBuf[MAX_LEN];
		sprintf_s(szErrBuf, MAX_LEN, "Invalid shout object");
		emit logged(szErrBuf);
#endif
		return 1;
	}

	if (!data || size <= 0)
	{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
		char szErrBuf[MAX_LEN];
		sprintf_s(szErrBuf, MAX_LEN, "Invalid data: pointer=0x%.8x, size=%d", data, size);
		emit logged(szErrBuf);
#endif
		return 2;
	}

/*
	//int bufSize = 1024;
	int bufSize = 2048;
	//const unsigned char *tmp = data;

	// copy all data
	unsigned char *tmp = (unsigned char*)malloc(size);
	memcpy(tmp, data, size);

	int tmpSize = size;
	while (tmpSize > 0)
	{
		int sending = tmpSize > bufSize ? bufSize : tmpSize;
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
			char szErrBuf1[MAX_LEN];
			sprintf_s(szErrBuf1, MAX_LEN, "Before sending: %d",sending);
			emit logged(szErrBuf1);
#endif
		int ret = shout_send(shout, tmp, sending);

#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
			char szErrBuf2[MAX_LEN];
			sprintf_s(szErrBuf2, MAX_LEN, "After sending: %d",sending);
			emit logged(szErrBuf2);
#endif
		if (ret != SHOUTERR_SUCCESS)
		{
#if defined(DEBUG) && (defined(_WIN32) || defined(_WIN64))
			char szErrBuf[MAX_LEN];
			sprintf_s(szErrBuf, MAX_LEN, "Error sending (%d bytes): %s", size, shout_get_error(shout));
			emit logged(szErrBuf);
#endif
			if (ret == SHOUTERR_SOCKET)
				return -1;
			continue;
		}

		shout_sync(shout);

		tmp += sending;
		tmpSize -= sending;
	}

	shout_sync(shout);
	
	//free(tmp);
*/
	if (shout_send(shout, data,size) != SHOUTERR_SUCCESS) {
		return -1;
	}

	if(shout_queuelen(shout) > 0) 
	{
		//fprintf(stderr, MNL "OGGFWD - queue length: %lu\n", (unsigned long) shout_queuelen(myShout));
	}

	shout_sync(shout);

	return 0;
}

void StreamConnection::run()
{
	m_bExit = 0;
	while (!m_bExit)
	{
		SendData * sdata = 0;
		m_queueLock.lock();
		if (m_queueData.size() > 0)
			sdata = m_queueData.dequeue();
		m_queueLock.unlock();

		if (sdata)
		{
			sendShout((const unsigned char *)sdata->data, sdata->size);
			free(sdata->data);
			free(sdata);
		}

		QThread::msleep(1);
	}
}

int StreamConnection::send(const unsigned char* header, const int header_len,const unsigned char *data, const int size)
{
	return sendShout(header, header_len, data, size);

	SendData* d = (SendData*)malloc(sizeof(SendData));
	d->data = (char*)malloc(size);
	memcpy(d->data, data, size);
	d->size = size;

	m_queueLock.lock();
	m_queueData.enqueue(d);
	m_queueLock.unlock();

	return size;
}

int StreamConnection::sendRaw(const unsigned char *data, const int size)
{
	int ret = 0;

	return ret;
}
